package com.gxitsky.classload.demo1;


import com.alibaba.fastjson.JSONObject;
import com.gxitsky.common.utils.FileUtils;
import com.gxitsky.common.utils.HttpClient;
import com.gxitsky.common.utils.RSAUtil;
import org.reflections.Reflections;
import org.reflections.vfs.Vfs;

import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Key;
import java.util.*;

/**
 * @author zhangjiaqi
 * @desc: 加密容器类 全程流操作 不会中途生成文件
 * @time 2021/4/9 11:18
 */
public class ContainerApp {

    private static ContainerConfig config = null;
    public static final String USER_DIR = System.getProperty("user.dir");
    private static final String encryptPath = USER_DIR + File.separator + "encrypt.tar.gz";
    public static final String CONTAINER_FILE = "CONTAINER_FILE";
    public static final String CONTAINER_TYPE = "container";
    private static ContainerLoader loader = null;

    private ContainerApp() {
    }

    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("args size error !!!");
            return;
        }

        // 初始化配置
        initConf();

        // 生成对象为了调用父类加载器
        ContainerApp app = new ContainerApp();

        // 解压缩二进制流
        HashMap<String, byte[]> allFiles = unTarFile();

        // 网络取RSA数据
        String data = getRsa();

        // 分析密文key
        Key key = parseAES(data);

        loader = new ContainerLoader(app.getClass().getClassLoader(), key, allFiles);

        app.startContainer(allFiles, args[0], args[1]);

    }

    public static ContainerLoader getLoader() {
        return loader;
    }

    /**
     * 初始化配置
     */
    public static void initConf() {
        String cfgText = FileUtils.getStringFromFile(System.getProperty(CONTAINER_FILE));
        config = JSONObject.parseObject(cfgText, ContainerConfig.class);
    }

    /**
     * 解压缩文件 转换为二进制流
     *
     * @return
     */
    public static HashMap<String, byte[]> unTarFile() {
        // 解压
        HashMap<String, byte[]> allFiles = null;
        try {
            File encryptFile = new File(encryptPath);
            if (!encryptFile.exists()) {
                System.out.println("encrpt file is null, path:" + encryptPath);
                System.exit(1);
            }

            allFiles = FileUtils.unTarGz(encryptFile);

        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        return allFiles;
    }

    /**
     * 开始容器
     *
     * @param allFiles    加密后的文件二进制流
     * @param mainClass   游戏服启动类
     * @param startMethod 游戏服启动方法
     */
    private void startContainer(HashMap<String, byte[]> allFiles, String mainClass, String startMethod) {
        try {

            // 解析后的 class二进制流
            List<Vfs.File> fileList = new ArrayList<>();
            for (String name : allFiles.keySet()) {
                byte[] bytes = loader.getData(name);
                Vfs.File file = new ContainerFile(name, name, bytes);
                fileList.add(file);
            }

            // 自定义容器一个加载类型，Reflections构造方法用
            ContainerType type = new ContainerType(new ContainerDir(fileList));
            Vfs.addDefaultURLTypes(type);

            Class<?> clazz = loader.loadClass(mainClass);
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Method method = clazz.getDeclaredMethod(startMethod);
            method.setAccessible(true);
            method.invoke(constructor.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 分析AES
     *
     * @param data
     * @return
     */
    public static Key parseAES(String data) {
        Key key = null;
        try {
            JSONObject resp = JSONObject.parseObject(data);
            String dataEncrypt = resp.getJSONObject("data").getString("data");
            String signS = resp.getJSONObject("data").getString("sign");
            String dataDecrrypt = RSAUtil.decryptStr(dataEncrypt, config.getPrivateKey());
            boolean ok = RSAUtil.verifySign(dataEncrypt, signS, config.getPublicKey());
            if (!ok) {
                System.out.println("sign verify fail!!!");
                return null;
            }
            // 只用16位密文
            if (dataDecrrypt.length() > 16) {
                dataDecrrypt = dataDecrrypt.substring(0, 16);
            }
            key = new SecretKeySpec(dataDecrrypt.getBytes(), "AES");
            return key;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取RSA秘钥
     *
     * @return
     */
    public static String getRsa() {

        String cfgText = null;
        try {
            cfgText = FileUtils.getStringFromFile(System.getProperty(EnvParamName.CONFIG_FILE.name()));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        JSONObject startJSON = JSONObject.parseObject(cfgText);

        String gameName = startJSON.getString("game").toUpperCase();
        String serverId = startJSON.getString("id");
        String version = startJSON.getString("platform").toUpperCase();


        String rtn = null;
        try {
            String ptext = "gameName" + gameName + "serverId" + serverId + "version" + version;
            String sign = RSAUtil.sign(ptext, config.getPrivateKey());

            JSONObject j = new JSONObject();
            j.put("serverId", serverId);
            j.put("version", version);
            String data = RSAUtil.encryptStr(j.toJSONString(), config.getPublicKey());

            Map<String, Object> map = new HashMap<>();
            map.put("gameName", gameName);
            map.put("data", data);
            map.put("sign", sign);
            rtn = HttpClient.doPost(config.getDecryptUrl(), map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rtn;
    }


    /**
     * 生成一个 容器的类扫描地址
     *
     * @return
     */
    private static URL createContainerURL() {
        URL u = null;
        try {
            u = new URL(ContainerApp.CONTAINER_TYPE, null, -1, "", new ContainerHandler());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return u;
    }


    /**
     * 扫描类文件
     *
     * @param packagePath
     * @param clazz
     * @return
     */
    public static <T> Set<Class<? extends T>> scanClass(String packagePath, Class<T> clazz) {
        Reflections reflections = null;
        // 容器启动 添加容器扫描地址
        if (loader != null) {
            reflections = new Reflections(packagePath, createContainerURL(), loader);
        } else {
            reflections = new Reflections(packagePath);
        }

        return reflections.getSubTypesOf(clazz);
    }

    /**
     * 扫描注解文件
     *
     * @param packagePath
     * @param annotation
     * @return
     */
    public static Set<Class<?>> scanAnnotation(String packagePath, Class<? extends Annotation> annotation) {
        Reflections reflections = null;
        // 容器启动 添加容器扫描地址
        if (loader != null) {
            reflections = new Reflections(packagePath, createContainerURL(), loader);
        } else {
            reflections = new Reflections(packagePath);
        }

        return reflections.getTypesAnnotatedWith(annotation);
    }

    /**
     * 给定名字扫描class
     *
     * @param name
     * @throws ClassNotFoundException
     */
    public static Class<?> forNameClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (loader == null) {
            clazz = Class.forName(name);
        } else {
            // 使用给定加载器加载
            clazz = Class.forName(name, true, loader);
        }
        return clazz;
    }
}